﻿using ExcelUnity.Core;
using ExcelUnity.Exporter.Exporters;
using ExcelUnity.Exporter.Styles;
using ExcelUnity.Exporter.Wrappers;
using ExcelUnity.Exporter.Wrappers.SheetWrapperBuilders;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ExcelUnity.Exporter
{
    public class DefaultExcelExporter : IExcelExporter
    {
        ///<inheritdoc cref="IExcelExporter.Export(ExportBook, string)"/>
        public void Export(ExportBook book, string filePath)
        {
            using var outStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
            Export(book, outStream);
        }

        ///<inheritdoc cref="IExcelExporter.Export(ExportBook, Stream)"/>
        public void Export(ExportBook exportBook, Stream stream)
        {
            if (stream == null) throw new ArgumentNullException("stream is null");
            if (!exportBook.Sheets?.Any() ?? true) throw new ArgumentNullException("sheets is empty");

            if (exportBook.FileType == FileType.Csv)
                ExportCsv(exportBook, stream);
            else
                ExportExcel(exportBook, stream);
        }

        ///<inheritdoc cref="IExcelExporter.Export(ExportBook)"/>
        public byte[] Export(ExportBook exportBook)
        {
            using var stream = new MemoryStream();
            Export(exportBook, stream);
            return stream.ToArray();
        }

        ///<inheritdoc cref="IExcelExporter.Export(FileType, Action{IWorkbook})"/>
        public byte[] Export(FileType fileType, Action<IWorkbook> workBookAction)
        {
            if (fileType != FileType.Xls && fileType != FileType.Xlsx)
                throw new ArgumentNullException("fileType is invalid");

            if (workBookAction == null)
                throw new ArgumentNullException(nameof(workBookAction));

            using var stream = new MemoryStream();
            var workBook = WorkbookHelper.CreateWorkbook(fileType);
            try
            {
                workBookAction.Invoke(workBook);
                workBook.Write(stream);
                return stream.ToArray();
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                workBook?.Close();
            }
        }

        ///<inheritdoc cref="IExcelExporter.Export(FileType, Stream, Action{IWorkbook})"/>
        public void Export(FileType fileType, Stream stream, Action<IWorkbook> workBookAction)
        {
            if (fileType != FileType.Xls && fileType != FileType.Xlsx)
                throw new ArgumentNullException("fileType is invalid");

            if (workBookAction == null)
                throw new ArgumentNullException(nameof(workBookAction));

            var workBook = WorkbookHelper.CreateWorkbook(fileType);
            try
            {
                workBookAction.Invoke(workBook);
                workBook.Write(stream);
            }
            catch (Exception ex)
            {
                throw;
            }
            finally
            {
                workBook?.Close();
            }
        }

        ///<inheritdoc cref="IExcelExporter.CreateCustomerContnetToSheet"/>
        public void CreateCustomerContnetToSheet(IWorkbook workBook, ExportSheetConfig sheetConfigDto, IEnumerable<ExportCustomerSheetCell> sheetCellList,
    List<(int columnIndex, int? minWidth, int? maxWidth)>? columnWidths = null)
        {
            var sheet = workBook.CreateSheet(sheetConfigDto.SheetName);
            //禁用网格线
            sheet.DisplayGridlines = sheetConfigDto.DisplayGridlines;
            //设置列宽
            SetColumnWidth(sheet, columnWidths);
            AppendCustomerContentToSheet(workBook, sheet, sheetCellList);
        }

        ///<inheritdoc cref="IExcelExporter.AppendCustomerContentToSheet"/>
        public void AppendCustomerContentToSheet(IWorkbook workBook, ISheet sheet, IEnumerable<ExportCustomerSheetCell> sheetCellList,
            List<(int columnIndex, int? minWidth, int? maxWidth)>? columnWidths = null)
        {
            //设置列宽
            SetColumnWidth(sheet, columnWidths);
            foreach (var rowCells in sheetCellList.OrderBy(t => t.Row).GroupBy(t => t.Row))
            {
                var row = sheet.GetOrCreateRow(rowCells.Key);
                //设置宽度为当前行中最大宽度的值
                var maxRowHeight = rowCells.Max(t => t.Heigth);
                if (maxRowHeight > 1) row.Height = (short)(maxRowHeight * 20);

                foreach (var item in rowCells)
                {
                    if (item.MergeConfig == null)
                    {
                        var excelCell = row.CreateCell(item.Column);
                        excelCell.SetCellType(item.CellType);
                        excelCell.SetCellValue(item.Value);
                        if (item.BaseCellStype != null)
                            excelCell.CellStyle = CreateCellStyle(workBook, item.BaseCellStype);

                        continue;
                    }

                    //需要合并的单元格
                    var (currRow, endRow, firstCol, endCol) = item.MergeConfig.GetCellRangeAddress(item.Row, item.Column);
                    //给合并列进行赋值处理
                    for (int i = currRow; i < endRow + 1; i++)
                    {
                        for (int j = firstCol; j < endCol + 1; j++)
                        {
                            var mergeRow = i == currRow ? row : sheet.GetRow(i) ?? sheet.CreateRow(i);
                            var excelCell = mergeRow.CreateCell(j);
                            excelCell.SetCellType(CellType.String);
                            excelCell.SetCellValue(item.Value);
                            if (item.BaseCellStype != null)
                                excelCell.CellStyle = CreateCellStyle(workBook, item.BaseCellStype);
                        }
                    }
                    sheet.AddMergedRegionUnsafe(new CellRangeAddress(currRow, endRow, firstCol, endCol));
                }
            }
        }

        ///<inheritdoc cref="IExcelExporter.CreateTitleAndTableAndCustomerSheet(IWorkbook, ExportSheet, Action{ISheet, int})"/>
        public void CreateTitleAndTableAndCustomerSheet(IWorkbook workBook, ExportSheet exportBookSheet, Action<ISheet, int> sheetInfo)
        {
            var sheetWrapper = ExportSheetWrapperBuilder.BuildSheetWrapper(exportBookSheet);
            if (sheetWrapper == null)
                return;

            var sheet = exportBookSheet.SheetConfig?.SheetName.IsNullOrWhiteSpace() != false ? workBook.CreateSheet() : workBook.CreateSheet(exportBookSheet.SheetConfig.SheetName);

            FillSheet(workBook, sheet, sheetWrapper);
            sheetInfo.Invoke(sheet, sheetWrapper.Rows.Count);
        }

        ///<inheritdoc cref="IExcelExporter.CreateTitleAndTableSheet(IWorkbook, ExportSheet)"/>
        public void CreateTitleAndTableSheet(IWorkbook workBook, ExportSheet exportBookSheet)
        {
            var sheetWrapper = ExportSheetWrapperBuilder.BuildSheetWrapper(exportBookSheet);
            if (sheetWrapper == null)
                return;

            var sheet = exportBookSheet.SheetConfig?.SheetName.IsNullOrWhiteSpace() != false ? workBook.CreateSheet() : workBook.CreateSheet(exportBookSheet.SheetConfig.SheetName);

            FillSheet(workBook, sheet, sheetWrapper);
        }

        #region 私有方法

        /// <summary>
        ///将csv导出流
        /// </summary>
        /// <param name="exportBook"></param>
        /// <param name="stream"></param>
        private static void ExportCsv(ExportBook exportBook, Stream stream)
        {
            var sheetWrapper = ExportSheetWrapperBuilder.BuildSheetWrapper(exportBook.Sheets.First());
            if (sheetWrapper == null) return;

            var csvRows = sheetWrapper.Rows.ConvertAll(row => new CsvRow(row.RowIndex, sheetWrapper.Columns.Count, row.Cells.Select(x => new CsvCell(x.ColumnIndex, x.Value))));
            var csvSheet = new CsvSheet(csvRows);
            csvSheet.Write(stream);
        }

        private readonly ConcurrentDictionary<string, ICellStyle> _cacheCellStyles = new ConcurrentDictionary<string, ICellStyle>();

        /// <summary>
        /// 将excel导出流
        /// </summary>
        /// <param name="exportBook"></param>
        /// <param name="stream"></param>
        private void ExportExcel(ExportBook exportBook, Stream stream)
        {
            var workBook = WorkbookHelper.CreateWorkbook(exportBook.FileType);

            try
            {
                foreach (var exportBookSheet in exportBook.Sheets!)
                {
                    var sheetWrapper = ExportSheetWrapperBuilder.BuildSheetWrapper(exportBookSheet);

                    var sheet = exportBookSheet.SheetConfig?.SheetName.IsNullOrWhiteSpace() != false ? workBook.CreateSheet() : workBook.CreateSheet(exportBookSheet.SheetConfig.SheetName);
                    if (sheetWrapper == null) continue;

                    FillSheet(workBook, sheet, sheetWrapper);
                }
                workBook.Write(stream);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                workBook?.Close();
            }
        }

        /// <summary>
        /// 填充sheet
        /// </summary>
        /// <param name="workBook"></param>
        /// <param name="sheet"></param>
        /// <param name="sheetWrapper"></param>
        private void FillSheet(IWorkbook workBook, ISheet sheet, ExportSheetWrapper sheetWrapper)
        {
            SetColumnWidth(sheet, sheetWrapper.Columns);

            foreach (var row in sheetWrapper.Rows)
            {
                var excelRow = sheet.CreateRow(row.RowIndex);

                foreach (var cell in row.Cells)
                {
                    var excelCell = excelRow.CreateCell(cell.ColumnIndex);
                    excelCell.SetCellValue(cell.Value);

                    if (cell.CellStyle != null)
                        excelCell.CellStyle = CreateCellStyle(workBook, cell.CellStyle);
                }

                //如果设置了行高
                if (row.RowHeight > 0 && excelRow.Height < row.RowHeight * 20)
                {
                    excelRow.Height = (short)(row.RowHeight * 20);
                }
            }

            Merged(sheetWrapper.Rows, sheet);

            static void SetColumnWidth(ISheet sheet, List<ExportColumnWrapper> columns)
            {
                foreach (var item in columns)
                {
                    sheet.AutoSizeColumn(item.ColumnIndex);
                    if (!item.MinWidth.HasValue && !item.MaxWidth.HasValue)
                        continue;

                    var width = (int)(sheet.GetColumnWidth(item.ColumnIndex) * 1.2);

                    if (item.MinWidth.HasValue && width < item.MinWidth)
                    {
                        width = item.MinWidth.Value;
                        sheet.SetColumnWidth(item.ColumnIndex, width);
                    }
                    else if (item.MaxWidth.HasValue && width > item.MaxWidth)
                    {
                        width = item.MaxWidth.Value;
                        sheet.SetColumnWidth(item.ColumnIndex, width);
                    }
                }
            }
        }

        /// <summary>
        /// 合并列
        /// </summary>
        /// <param name="listData"></param>
        /// <param name="sheet"></param>
        private void Merged(List<ExportRowWrapper> listData, ISheet sheet)
        {
            var rowMerges = new Dictionary<int, List<(int BeginRow, int EndRow, string? CompareValue)>>();
            var columnMerges = new List<(int RowIndex, List<List<int>> ColumnIndexs)>();

            foreach (var row in listData)
            {
                var rowMergeColumns = new List<List<int>>();
                foreach (var cell in row.Cells)
                {
                    if (cell.MergeColumn)
                    {
                        //最后一组需要合并的列
                        var lastList = rowMergeColumns.LastOrDefault();
                        if (lastList == null)
                        {
                            rowMergeColumns.Add(new List<int> { cell.ColumnIndex });
                        }
                        else
                        {
                            //最后一个需要合并的列索引
                            var lastColumnIndex = lastList.Last();

                            //如果和当前列是相邻列
                            if (lastColumnIndex == cell.ColumnIndex - 1)
                                lastList.Add(cell.ColumnIndex);
                            //如果不是相邻列，且上一组合并列只有一个列，移除
                            else if (lastList.Count == 1)
                                rowMergeColumns.RemoveAt(rowMergeColumns.Count - 1);
                        }
                    }
                    //如果该单元格不是单独合并并且不是根据主键合并
                    if (!cell.MergedRowAlone && !cell.MergedRowByPrimaryKey) continue;

                    //判断合并行对比值，主键或者当前列值
                    var compareValue = cell.MergedRowByPrimaryKey ? row.PrimaryKey : cell.Value;

                    //当前列当前行,取到最后一个合并行
                    if (!rowMerges.TryGetValue(cell.ColumnIndex, out List<(int _0, int _1, string? _2)> preDataList))
                    {
                        rowMerges.Add(cell.ColumnIndex, new List<(int BeginRow, int EndRow, string? CompareValue)>() { (row.RowIndex, 0, compareValue) });
                        continue;
                    }

                    var (lastBeginRow, lastEndRow, lastCompareValue) = preDataList.Last();
                    //当前行与上一行相等，重置EndRow
                    if (lastCompareValue == compareValue)
                    {
                        preDataList.RemoveAt(preDataList.Count - 1);
                        preDataList.Add((lastBeginRow, row.RowIndex, lastCompareValue));
                    }
                    else
                    {
                        preDataList.Add((row.RowIndex, 0, compareValue));
                    }
                }
                //上一组合并列只有一个列，移除
                if (rowMergeColumns.Count > 0 && rowMergeColumns.Last().Count == 1)
                    rowMergeColumns.RemoveAt(rowMergeColumns.Count - 1);

                if (rowMergeColumns.IsNotNullOrEmpty())
                    columnMerges.Add((row.RowIndex, rowMergeColumns));
            }

            foreach (var merge in rowMerges)
            {
                foreach (var (beginRow, endRow, _) in merge.Value.Where(x => x.EndRow > 0))
                    sheet.AddMergedRegion(new CellRangeAddress(beginRow, endRow, merge.Key, merge.Key));
            }

            foreach (var (rowIndex, columnIndexs) in columnMerges)
            {
                foreach (var columns in columnIndexs)
                    sheet.AddMergedRegion(new CellRangeAddress(rowIndex, rowIndex, columns[0], columns.Last()));
            }
        }

        /// <summary>
        /// 获取/设置单元格样式
        /// </summary>
        /// <param name="workBook"></param>
        /// <param name="style"></param>
        /// <returns></returns>
        private ICellStyle CreateCellStyle(IWorkbook workBook, BaseStyle style)
        {
            return _cacheCellStyles.GetOrAdd(style.ToString(), (_) =>
            {
                var cellStyle = workBook.CreateCellStyle();
                var font = workBook.CreateFont();
                font.IsBold = style.IsBold;
                font.FontName = style.FontName;
                font.FontHeightInPoints = style.FontSize;
                font.Color = style.FontColor;
                cellStyle.SetFont(font);
                cellStyle.Alignment = style.HorizontalAlign;
                cellStyle.VerticalAlignment = style.VerticalAlign;
                cellStyle.Alignment = style.HorizontalAlign;
                cellStyle.WrapText = style.WrapText;
                if (style.FillForegroundColor.HasValue)
                {
                    cellStyle.FillPattern = FillPattern.SolidForeground;
                    cellStyle.FillForegroundColor = style.FillForegroundColor.Value;
                }
                cellStyle.BorderBottom = style.BorderBottom;
                cellStyle.BorderLeft = style.BorderLeft;
                cellStyle.BorderRight = style.BorderRight;
                cellStyle.BorderTop = style.BorderTop;
                cellStyle.BottomBorderColor = style.BottomBorderColor;
                cellStyle.LeftBorderColor = style.LeftBorderColor;
                cellStyle.RightBorderColor = style.TopBorderColor;
                cellStyle.TopBorderColor = style.TopBorderColor;

                return cellStyle;
            });
        }

        /// <summary>
        /// 设置列宽度
        /// </summary>
        /// <param name="sheet"></param>
        /// <param name="columns"></param>
        private static void SetColumnWidth(ISheet sheet, List<(int columnIndex, int? minWidth, int? maxWidth)>? columns)
        {
            if (columns == null || columns.Count == 0)
                return;

            foreach (var (columnIndex, minWidth, maxWidth) in columns)
            {
                sheet.AutoSizeColumn(columnIndex);
                if (!minWidth.HasValue && !maxWidth.HasValue)
                    continue;

                var width = (int)(sheet.GetColumnWidth(columnIndex) * 1.2);

                if (minWidth.HasValue && width < minWidth)
                {
                    width = minWidth.Value;
                    sheet.SetColumnWidth(columnIndex, width);
                }
                else if (maxWidth.HasValue && width > maxWidth)
                {
                    width = maxWidth.Value;
                    sheet.SetColumnWidth(columnIndex, width);
                }
            }
        }

        #endregion
    }
}